import os
import sys
import tagpy
import xml.dom.minidom
import re

#this func ensure path exists, if not it creates it.
def ensure_dir(d):
    if not os.path.exists(d):
        os.makedirs(d)
        
#this function gets a file's name in the format: artist - albume - treck - name.mp3 and
#will return map to the name's parts
def get_song_file_properties(file_path, file_name):
    try:
        name, file_extension = os.path.splitext(file_name)
        
        song_parts = [x.strip().title() for x in name.split('-')]
        
        if (file_extension.lower() != '.mp3'):
            return None
        
        if (len(song_parts[2]) == 1):
            song_parts[2] = '0' + song_parts[2]
             
        return {'artist':song_parts[0], 'album':song_parts[1], 'track':song_parts[2], 'name':song_parts[3], 'path':os.path.join(file_path, file_name)}
    except:
        return None

#this function updates an mp3 id3 tags according the info we extracted from the file's name
def set_id_tag(song_path, song_info):
    f = tagpy.FileRef(song_path);
    tags = f.tag()
    tags.artist = song_info['artist'];
    tags.album = song_info['album']
    tags.title = song_info['name']
    tags.track = int(song_info['track'])
    f.save();

#this function generates the xml section for a single song for the playlist
def generate_song_xml(location, creator, album, title, track):
    return '<track><location>' + location + '</location><creator>' + creator + '</creator><album>' + album + '</album><title>' + title + '</title><trackNum>' + track + '</trackNum></track>'

#this function gets an ugly xml and generates a pretty xml out of it
def generate_pretty_xml(xml_str):
    my_xml = xml.dom.minidom.parseString(xml_str)
    text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
    uglyXml = my_xml.toprettyxml(indent='  ', encoding='UTF-8')
    return text_re.sub('>\g<1></', uglyXml).replace('<?xml version="1.0" encoding="UTF-8"?>', '<?xml version="1.0" encoding="UTF-8" ?>')
 
#this function generate a list of sub folders (from a given path) from the inner to the outer
def get_directories_down_to_top(path):
    dirs_list = []
    for dirContent in os.walk(path):
        dirs_list.append(dirContent[0])
    
    dirs_list.reverse()
    
    return dirs_list[0:-1]

#this function deletes all empty folders from a given list
def delete_empty_folders(dir_list):
    for cur_dir in dir_list:
        if (os.listdir(cur_dir) == []):
            os.rmdir(cur_dir)

#this function adds a song to our DAST that holds all songs according artists and albums
def add_song_to_db(songs_db, song):
    if (not songs_db.has_key(song['artist'])):
        songs_db[song['artist']] = {}
    
    artist = songs_db[song['artist']]
    
    if (not artist.has_key(song['album'])):
        artist[song['album']] = []
        
    album = artist[song['album']]
    
    album.append(song)

#this function creates a songs db of a given path    
def get_songs_db(path):
    songs_db = {}
    for dirContent in os.walk(path):
        for name in dirContent[2]:
            song = get_song_file_properties(dirContent[0], name)
            
            if (song == None):
                continue
            
            add_song_to_db(songs_db, song)
    
    return songs_db

#this function rearranges a path full of songs according the exercise specification
def rearrange_db_on_drive(path, songs_db):
    for artist_name, artist in songs_db.items():
        artist_path = os.path.join(path, artist_name)
        ensure_dir(artist_path)
        
        for album_name, album in artist.items():
            album_path = os.path.join(artist_path, album_name)
            ensure_dir(album_path)
            
            for song in album:
                song_new_path = os.path.join(album_path, song['track'] + ' - ' + song['name'] + '.mp3')
                os.rename(song['path'], song_new_path)
                set_id_tag(song_new_path, song)


#this function creates a playlist for an album
def create_album_playlist(artist_name, album_name, songs_list, playlist_path):
    xml_str = '<?xml version="1.0" encoding="UTF-8" ?><playlist version="1" xmlns="http://xspf.org/ns/0/"><title>' + artist_name + ' : ' + album_name + '</title><trackList>'
    ordered_songs = [song['track'] + ' - ' + song['name'] for song in songs_list]
    ordered_songs.sort()
    for song in ordered_songs:
        track_number = song.split('-')[0].strip()
        song_name = song.split('-')[1].strip()
        
        xml_str += generate_song_xml(os.path.join(album_name, song + '.mp3'), artist_name, album_name, song_name, track_number)
    
    xml_str += '</trackList></playlist>'
    
    f = open(playlist_path, 'w')
    f.write(generate_pretty_xml(xml_str))
    f.close()
    
#this function creates a playlist for every album in the db
def create_playlists_for_songs_db(path, songs_db):
    for artist_name, artist in songs_db.items():
        
        artist_path = os.path.join(path, artist_name)
        
        for album_name, album in artist.items():
            create_album_playlist(artist_name, album_name, album, os.path.join(artist_path, album_name + '.xspf'))
                 
    
#main entry
if __name__ == '__main__':
    
    args = sys.argv[1:]
    
    if (len(args) != 1):
        print('Usage: python ex1.py [path of music folder]')
        sys.exit()
        
    main_dir_path = args[0]
    
    #0. save current dirs for further emptiness check
    directories = get_directories_down_to_top(main_dir_path)
    
    #1. build the artists-albums-songs database
    songs_db = get_songs_db(main_dir_path)
    
    #2. store all songs at the relevant hierarchy
    rearrange_db_on_drive(main_dir_path, songs_db)
    
    #3. create playlists
    create_playlists_for_songs_db(main_dir_path, songs_db)
    
    #4. delete an empty directories
    delete_empty_folders(directories)
